/* interface/hipinmul/hipinmul.c
 *
 * Copyright (c) 2006 Hisilicon Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * History:
 *      16-12-2006 Start of Hi3560 CPU support
 */


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/sched.h>
//#include <asm/hardware.h>
#include <linux/interrupt.h>

#include <linux/poll.h>
#include <linux/miscdevice.h>
#include <linux/errno.h>
#include <linux/fcntl.h>

#include <linux/init.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/workqueue.h>

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/version.h>

#include "pinmul.h"

#define HI_MUXCTL_BASE  (IO_ADDRESS(0x200F0000))
#define HI_MUXCTL_REG69 (0x114) //GPIO5_5 /SPI1_SDO
#define HI_MUXCTL_REG66 (0x108)
#define HI_MUXCTL_REG67 (0x10c)

#define HI_GPIO 0
#define HI_SFUN 1

#define HI_GPIO0_BASE (IO_ADDRESS(0x20140000))
#define HI_GPIO1_BASE (IO_ADDRESS(0x20150000))
#define HI_GPIO2_BASE (IO_ADDRESS(0x20160000))
#define HI_GPIO3_BASE (IO_ADDRESS(0x20170000))
#define HI_GPIO4_BASE (IO_ADDRESS(0x20180000))
#define HI_GPIO5_BASE (IO_ADDRESS(0x20190000))
#define HI_GPIO6_BASE (IO_ADDRESS(0x201A0000))
#define HI_GPIO7_BASE (IO_ADDRESS(0x201B0000))
#define HI_GPIO8_BASE (IO_ADDRESS(0x201C0000))
#define HI_GPIO9_BASE (IO_ADDRESS(0x201D0000))
#define HI_GPIO10_BASE (IO_ADDRESS(0x201E0000))
#define HI_GPIO11_BASE (IO_ADDRESS(0x201F0000))

#define GPIO_DAT(x) (HI_GPIO5_BASE + (1 << (2 + (x))))
#define GPIO_DIR    (0x400)
#define GPIO_IS     (0x404)
#define GPIO_IBE    (0x408)
#define GPIO_IEV    (0x40c)
#define GPIO_IE     (0x410)
#define GPIO_RIS    (0x414)
#define GPIO_MIS    (0x418)
#define GPIO_IC     (0x41c)




/* define GPIO */
#define GPIO5_REG_BASE (IO_ADDRESS(0x20190000))
#define GPIO_MULT_BASE  (IO_ADDRESS(0x200F0000))

//IO_ADDRESS
#if 0
#define GPIO_5_5_CFG (GPIO_MULT_BASE + 0x114)
#define GPIO_DATA   (GPIO5_REG_BASE + 0x080)
#define GPIO_DIR    (GPIO5_REG_BASE + 0x400)
#define GPIO_IS     (GPIO5_REG_BASE + 0x404)
#define GPIO_IBE    (GPIO5_REG_BASE + 0x408)
#define GPIO_IEV    (GPIO5_REG_BASE + 0x40c)
#define GPIO_IE     (GPIO5_REG_BASE + 0x410)
#define GPIO_RIS    (GPIO5_REG_BASE + 0x414)
#define GPIO_MIS    (GPIO5_REG_BASE + 0x418)
#define GPIO_IC     (GPIO5_REG_BASE + 0x41c)
#endif


/* define macro */
#define WRITE_REG(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define READ_REG(Addr)         (*(volatile unsigned int *)(Addr))

/* debug */
static int g_dbg_flag = 0;
#define HIPINMUL_PFX "hipinmul: "
#define hipinmul_dbg(params...) \
    do{ \
        if(g_dbg_flag) \
        { \
            printk(KERN_DEBUG HIPINMUL_PFX params); \
        } \
    }while(0);



static void hipinmul_config( void )
{
    int iev,dirv;

    #if 1
    printk("set serial pin mul func\n");
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG66, HI_SFUN);
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG67, HI_SFUN);
    #else
    /* TODO: ASIC config pin share */
    WRITE_REG(HI_MUXCTL_BASE+HI_MUXCTL_REG69, HI_GPIO); //set as gpio
    
    iev = READ_REG(HI_GPIO5_BASE + GPIO_IE);
    WRITE_REG(HI_GPIO5_BASE + GPIO_IE, iev&0xDF);   //disable interrupt
    
    //WRITE_REG(GPIO_DAT(5), 0x20);
    
    dirv = READ_REG(HI_GPIO5_BASE + GPIO_DIR);
    WRITE_REG(HI_GPIO5_BASE + GPIO_DIR,dirv|0x20);  //set gpio5_5 as output
    #endif
    
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)
static int hipinmul_ioctl( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg )
#else
static long hipinmul_ioctl( struct file *filp, unsigned int cmd, unsigned long arg )
#endif
{
    //int __user *p = ( int __user * )arg;

    switch ( cmd )
    {
        case GPIO_IOC_DIR:
            break;
            
        case GPIO_IOC_PORT:
            break;
        case GPIO_IOC_DATA:
            if (arg)
            {
                WRITE_REG(GPIO_DAT(5), 0x20);
            }
            else
            {
                WRITE_REG(GPIO_DAT(5), 0x00);
            }
            break;
        default:
            printk( KERN_DEBUG HIPINMUL_PFX "Error: Inappropriate ioctl for device. cmd=%d\n", cmd );
            return -ENOTTY;
    }

    //hipinmul_config();

    return 0;
}

static ssize_t hipinmul_read( struct file *filp, char __user *buf, size_t count, loff_t *f_pos )
{
    int res = 0;

    return res;
}

#if 0
static unsigned int hipinmul_select( struct file *filp, struct poll_table_struct *wait )
{
    hipinmul_dbg( "Enter hiir_select.\n" );
    if ( ( hiir_dev.head ) != ( hiir_dev.tail ) )
    {
        return 1;
    }
    poll_wait( filp, &( hiir_dev.irkey_wait ), wait );
    return 0;
}
#endif

static atomic_t hipinmul_s_available = ATOMIC_INIT( 1 );

static int hipinmul_open( struct inode *inode, struct file *filp )
{
    if ( ! atomic_dec_and_test( &hipinmul_s_available ) )
    {
        atomic_inc( &hipinmul_s_available );
        printk( KERN_DEBUG HIPINMUL_PFX "Error: device already open.\n" );
        return -EBUSY;
    }

    hipinmul_config();
    return 0;
}

static int hipinmul_release( struct inode *inode, struct file *filp )
{
    atomic_inc( &hipinmul_s_available );

    return 0;
}

static int hipinmul_write( struct file *filp, const char __user *buf, size_t count, loff_t *f_pos )
{
    return 0;
}

static struct file_operations hipinmul_fops =
{
owner   :
    THIS_MODULE,
open    :
    hipinmul_open,
unlocked_ioctl   :
    hipinmul_ioctl,
poll    :
    NULL,//hipinmul_select,
read    :
    hipinmul_read,
write   :
    hipinmul_write,
release :
    hipinmul_release,
};

static struct miscdevice hipinmul_miscdev =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name  = HIPINMUL_DEVICE_NAME,
    .fops  = &hipinmul_fops,
};

static int __init hipinmul_init( void )
{
    int res = 0;

    printk( KERN_INFO OSDRV_MODULE_VERSION_STRING "\n" );


    res = misc_register( &hipinmul_miscdev );
    if ( 0 != res )
    {
        printk( KERN_DEBUG HIPINMUL_PFX "Error: can't register\n" );
        return -EFAULT;
    }
    hipinmul_config();
    #if 0
    res = request_irq( HIIR_DEVICE_IRQ_NO, hiir_interrupt, 0, HIIR_DEVICE_NAME, &hiir_interrupt );
    if ( res )
    {
        printk( KERN_DEBUG HIPINMUL_PFX "Error: request IRQ(%d) failed\n", HIIR_DEVICE_IRQ_NO );
        misc_deregister( &hiir_miscdev );
        goto hi_ir_init_failed;
    }
    #endif
    printk( HIPINMUL_PFX "init ok. ver=%s, %s.\n", __DATE__, __TIME__ );

hi_gpio_init_failed:
    return res;
}

static void __exit hipinmul_exit( void )
{
    misc_deregister( &hipinmul_miscdev );
}

module_init( hipinmul_init );
module_exit( hipinmul_exit );
MODULE_LICENSE( "GPL" );
MODULE_DESCRIPTION( "Hisilicon PINMUL Device Driver" );
MODULE_VERSION( "HI_VERSION=" OSDRV_MODULE_VERSION_STRING );

